﻿#include "PluginManager.h"
#include "PluginManager_p.h"
#include <QDebug>
#include "ObjectsOperator.h"
#include "PluginSpec.h"
#include "PluginSpec_p.h"
namespace CVV
{

namespace Infrastructure
{

namespace ExtensionSystem
{

using namespace Utils;
using namespace Internal;

static PluginManager *m_instance = new PluginManager;
static Internal::PluginManagerPrivate *d =nullptr;// new PluginManagerPrivate(m_instance);

PluginManager *PluginManager::instance()
{
    return m_instance;
}


PluginManager::PluginManager()
{
    d = new PluginManagerPrivate(this);
}


PluginManager::~PluginManager()
{
    if (d)
    {
        delete d;
        d = nullptr;
    }
}


void PluginManager::addObject(QObject *obj)
{
    qDebug() << __func__;
    d->addObject(obj);
}


void PluginManager::removeObject(QObject *obj)
{
    qDebug() << __func__;
    d->removeObject(obj);
}

void PluginManager::clearPlugin()
{
    for(auto obj:allObjects()){
        d->removeObject(obj);
    }
}


QVector<QObject *> PluginManager::allObjects()
{
    return d->allObjects;
}

QStringList PluginManager::pluginPaths()
{
    return d->pluginPaths;
}


QString PluginManager::pluginIID()
{
    return d->pluginIID;
}
void PluginManager::setPluginIID(const QString &iid)
{
    d->pluginIID = iid;
}


QReadWriteLock *PluginManager::listLock()
{
    return &d->m_lock;
}

QObject *PluginManager::getObjectByName(const QString &name)
{
    QReadLocker lock(&d->m_lock);

    return Utils::findOrDefault(allObjects(), [&name](const QObject *obj) {
        return obj->objectName() == name;
    });
}

QString PluginManager::getPluginNameByCode(const QString &code)
{
    QReadLocker lock(&d->m_lock);
    PluginSpec* pSpec = Utils::findOrDefault(plugins(), [&code](const PluginSpec *obj) {
        return obj->code() == code;
    });

    if (pSpec)
    {
        return tr(pSpec->name().toStdString().c_str());
    }

    return "";
}

PluginSpec *PluginManager::getPluginSpecByCode(const QString &code)
{
    QReadLocker lock(&d->m_lock);
    PluginSpec* pSpec = Utils::findOrDefault(plugins(), [&code](const PluginSpec *obj) {
        return obj->code() == code;
    });

    return pSpec;
}

void PluginManager::setPluginPaths(const QStringList &paths)
{
    d->setPluginPaths(paths);
}

QVector<PluginSpec *> PluginManager::loadQueue()
{
    return d->loadQueue();
}
void PluginManager::loadPlugins()
{
    d->loadPlugins();
}
const QVector<PluginSpec *> PluginManager::plugins()
{
    return d->pluginSpecs;
}

const QMap<QString, QJsonArray> PluginManager::pluginParams()
{
    return d->pluginParams;
}

void PluginManager::addPluginParam(QString pluginCode, QJsonArray array)
{
    d->addPluginParam(pluginCode,array);
}

void PluginManager::removePluginParam(QString pluginCode)
{
  d->removePluginParam(pluginCode);
}
/********************PluginManager Private start **************************/
PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager)
    : q(pluginManager)
{
}

PluginManagerPrivate::~PluginManagerPrivate()
{

}

void PluginManagerPrivate::setPluginPaths(const QStringList &paths)
{
    qDebug()<< "Plugin search paths:" << paths;
    qDebug()<< "Required IID:" << pluginIID;
    pluginPaths = paths;
    readPluginPaths();
}

static const QStringList pluginFiles(const QStringList &pluginPaths)
{
    QStringList pluginFiles;
    QStringList searchPaths = pluginPaths;
    while (!searchPaths.isEmpty()) {
        const QDir dir(searchPaths.takeFirst());
        const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoSymLinks);
        const QStringList absoluteFilePaths = Utils::transform(files, &QFileInfo::absoluteFilePath);
        pluginFiles += Utils::filtered(absoluteFilePaths, [](const QString &path) { return QLibrary::isLibrary(path); });
        const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
        searchPaths += Utils::transform(dirs, &QFileInfo::absoluteFilePath);
    }
    return pluginFiles;
}
void PluginManagerPrivate::readPluginPaths()
{
    qDeleteAll(pluginSpecs);
    pluginSpecs.clear();
    for (const QString &pluginFile : pluginFiles(pluginPaths)) {
        PluginSpec *spec = PluginSpec::read(pluginFile);
        if (!spec) // not a Qt Creator plugin
            continue;
        pluginSpecs.append(spec);
    }
    // ensure deterministic plugin load order by sorting
    Utils::sort(pluginSpecs, &PluginSpec::name);
}

const QVector<PluginSpec *> PluginManagerPrivate::loadQueue()
{
    QVector<PluginSpec *> queue;
    for (PluginSpec *spec : qAsConst(pluginSpecs)) {

        if (spec->state() == PluginSpec::Invalid && spec->state() == PluginSpec::Read) {
            queue.append(spec);
            continue;
        }
        queue.append(spec);
    }

    return queue;
}
void PluginManagerPrivate::loadPlugins()
{
    const QVector<PluginSpec *> queue = loadQueue();
    //1.load plugin
    for (PluginSpec *spec : queue)
        loadPlugin(spec, PluginSpec::Loaded);

    //2. initialized plugin
    for (PluginSpec *spec : queue)
        loadPlugin(spec, PluginSpec::Initialized);
}
void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destState)
{
    if (spec->hasError())
        return;

    switch (destState)
    {
    case PluginSpec::Deleted:
        spec->d->kill();
        return;
    case PluginSpec::Loaded:
        spec->d->loadLibrary();
        break;
    case PluginSpec::Initialized:
        spec->d->initializePlugin();
        break;
    default:
        break;
    }
}

void PluginManagerPrivate::addObject(QObject *obj)
{

    QWriteLocker lock(&m_lock);
    if (obj == nullptr) {
        qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
        return;
    }
    if (allObjects.contains(obj)) {
        qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
        return;
    }

    allObjects.append(obj);

    emit q->objectAdded(obj);
}

void PluginManagerPrivate::removeObject(QObject *obj)
{
    if (obj == nullptr) {
        qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
        return;
    }

    if (!allObjects.contains(obj)) {
        qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
            << obj << obj->objectName();
        return;
    }

    emit q->aboutToRemoveObject(obj);
    QWriteLocker lock(&m_lock);
    allObjects.removeAll(obj);
}

void PluginManagerPrivate::addPluginParam(QString pluginCode,QJsonArray array)
{
    QWriteLocker lock(&m_lock);
    pluginParams.insert(pluginCode,array);
}

void PluginManagerPrivate::removePluginParam(QString pluginCode)
{
    QWriteLocker lock(&m_lock);
    pluginParams.remove(pluginCode);
}

}

}

}
